Speedy Integers
Last time I discussed how variables are defined in a SPEED/ASM program.
One byte must be reserved for a character variable, two bytes for an integer variable,
eight for a floating point variable, and (n +2) bytes must be reserved for a string variable.

Now I'll describe how to perform integer arithmetic using the SPEED/ASM package.

Before describing how to deal with integer values in SPEED/ASM, I think a brief review
concerning the declaration of integer variables is in order.

As I mentioned last time, SPEED/ASM integers require two bytes of storage in RAM memory.

While there are many ways to reserve two bytes of storage for an integer, I prefer to
define an integer variable using the ADR pseudo-opcode thus:

      <name> ADR 0

where <name> is the variable name with which I wish to reference the integer value.
For example, to declare the integer variables I, J and K, I would use the statements:

      I ADR 0
      J ADR 0
      K ADR 0

Integers on the 6502 consist of two bytes; the first eight bits (byte) comprise the
low order byte (L.O. byte) and the second eight bits of the integer comprise the
high order byte (H.O. byte).

A single byte can hold any numeric value in the range 0... 255.
Two bytes (taken as an unsigned integer) can be used to represent values in the
range 0. . .65535.

The SPEED/ASM package uses a modified form of the binary numbering system called the
two’s complement numbering system.

A pure binary numbering system cannot be used to represent negative values so the use of
the binary number system is quite restrictive. The two’s complement number system divides
the unsigned range in half and uses half of the possible values to represent the numbers
0...32767 and the other half of the available values to represent numbers in the range
-32768. . . -1.

Since SPEED/ASM uses the same two’s complement format employed by Basic, SPEED/ASM’s
numeric range (—32768...32767) is the same as Basic’s.

Always remember that SPEED/ASM variables must be defined outside the range of your code.

That is, during the execution of your program the 6502 must never jump to or fall through
to a variable location.

The 6502 would extract the data at that location and attempt to execute it as a valid
6502 instruction — usually with undesirable results.

A well written SPEED/ASM program will have its variable declarations at the end of the
program, after the JMP EXIT instruction (or whatever other method you use to terminate
program execution — see Part One of this series).

A good format for your SPEED/ASM programs is shown in Example 1.

Once you've defined an integer variable, the next step is to manipulate the data it holds.

There are essentially ten integer operations available to the SPEED/ASM programmer:
loading a variable with a value, copying the contents of one integer variable to another,
the absolute value function (ABS) , negation, addition, subtraction, multiplication,
division modulo (remainder) and the random number function. Beyond these computational
capabilities, the ability to input and output integers is also desirable.

      ;
      ;
      ; (Put the SPEED/ASM equates here)
      ;
      EXIT         EQU $FF69
      ;
      ;
                   JSR INIT          ; Always call INIT first
      ;
      ;
      ; (Your SPEED/ASM program goes here
      ;
      ;
                   JMP EXIT          ; Used to terminate the program
      ;
      ;
      ; Variable declarations go here, eg:
      ;
      I             ADR 0
      J             ADR 0
      ;
      ;etc 
      ;
                    END              ; Required by LISA for end of program.
                         Example 1.


CLC      ; Always before an addition     |  SEC      ; Carry must be set before subtraction
LDA I    ; Add L.O. byte of i to the     |  LDA I    ; Subtract the L.O. byte of J from
ADC J    ; L.O. byte of J and            |  SBC J    ; I and store the difference
STA K    ; store sum in L.O. byte of K.  |  STA K    ; into the L.O. byte of K
LDA I+1  ; Add H.O. byte of I to the     |  LDA I+1  ; Subtract the H.O. byte of J from
ADC J+1  ; H.O. byte of J and store the  |  SBC J+1  ; the H.O. byte of I and store the
STA K+1  ; sum in the H.O. byte of K     |  STA K+1  ; difference into the H.O. byte of K

 Example 2.                                    Example 3.


Addition and subtraction are handled so easily in 6502 machine code that SPEED/ASM doesn’t
include addition and subtraction routines. If you wanted to add I and J and store the sum
in K you would use the code in Example 2.

The CLC (clear carry) instruction absolutely must precede the addition sequence.
Failure to clear the carry flag before performing the addition operation may result in an
intermittent bug in your program. This addition sequence is almost identical to the
Basic statement:

      K = I + J

To perform a subtraction in 6502 assembly language (or SPEED/ASM), use the sequence
in Example 3. Note that the carry flag must be set (using the SEC instruction) before
performing the subtract sequence. Failure to set the carry before performing a subtraction
may yield unpredictable results. The subtraction sequence above is roughly equivalent to
the Basic statement:

      K = I - J

If you need to add a constant to an integer variable
(instead of adding two integer variables together)

the # and / operators can be used to specify constants in the, 6502 operand field.

The # is used to specify the L.O. byte of an integer constant and the / is used to specify
the H.O. byte of an integer constant.

If you wanted to add the constant 4369 to the integer variable I and leave the result in K
you would use the code in Example 4.

      CLC                     |      CLC
      LDA I                   |      LDA #!-5639
      ADC #4369               |      ADC J
      STA K                   |      STA K
      LDA I+1                 |      LDA /!-5639
      ADC /4369               |      ADC J+1
      STA K+                  |      STA K+1

     Example 4.                   Example 5.

This would produce the desired results.

If you wanted to use a negative constant, LISA v2.5 requires that you preface the negative
value with an exclamation mark. LISA v3.0 imposes no such restriction (see Example 5).

Testing for Overflow and Underflow
As I mentioned, the 6502 addition and subtraction operations are only rough approximations
of the listed Basic statements. The difference between the assembly-language and Basic
statements is in the way Basic checks for overflow or underflow.

In Basic, if you attempt to add 32000 to 32000 you will get a “>32767” error.
In assembly language you will end up with the value — 1536 in variable K, and no error
will be reported.

When I was learning addition in grade school I was taught that

     32000 + 32000 equals 64000, not —1536.

If you can live with a possible overflow or underflow, the above sequences should work
just fine. If you need to report an error if overflow occurs, you must check the 6502
overflow flag after performing an addition or subtraction. After an addition or subtraction
the 6502 overflow flag will be clear if the result is within range. and set if it is out
of range. The 6502 BVC (branch if overflow clear) and BVS branch if overflow set)
instructions can be used to check for an overflow or underflow condition (see Example 6).

Initializing and Copying Integer Variables
The MOVE and LOAD routines are used to copy and initialize integer variables in a
SPEED/ASM program. LOAD lets you initialize an integer variable with an integer constant
and MOVE lets you copy the contents of one integer variable into another.

The LOAD command uses the calling sequence:

      JSR LOAD
      ADR <value>,<name>

This routine copies the two-byte integer <value> into the variable specified by <name>.
For example, to load the value 3765 into the variable I you would use the statement:

      JSR LOAD
      ADR 3765,I

To load a negative number into the variable LISA 2.5 users must preface the negative number
with the exclamation point (!). To load —438 into the variable I you should use the
statement(s):

      JSR LOAD
      ADR !-438,I

LISA 3.0 users should omit the exclamation mark. These two statements are comparable to
the Basic statements:

     I= 3765

   and

      I= —438

respectively. Please note that the # and / operators are not required before the
constant values.

This is, an unfortunate inconsistency, so you should take extra care to avoid either
placing the # or / symbols here, or leaving the # and / symbols out of the operand field
of the 6502 LDA or other arithmetic instruction.

Remember, the LOAD routine is used to load a constant value into an integer variable.
If you use a variable name as the first operand to the LOAD routine, the address of that
variable, not its current contents, will be loaded into the destination variable.

     EXIT          EQU $FF69
      ;
                    CLC
                    LDA I
                    ADC J
                    LDA I+1
                    ADC J+1
                    STA K+1
                    BVC GOODADD                  ; If error then
                    JSR PRINT                    ; print in error
                    BYT "Error >32767",CR,0      ; message and
                    JMP EXIT                     ; quit the program.
      GOODADD       --- ---                      ; Continue here if
                    --- ---                      ; no overflow
                    SEC
                    LDA I
                    SBC J
                    STA K
                    LDA I+1
                    SBC J+1
                    STA K+1
                    BVC GOODSUB
                    JSR PRINT
                    BYT "Error <-32768",CR,0
                    JMP EXIT
      GOODSUB       --- ---
                    --- ---
                    Example 6.

The MOVE routine copies the contents of one integer variable into another.

The MOVE command uses the syntax:

      JSR MOVE
      ADR <name1>,<name2>

MOVE copies the contents of <name1> into <name2>.

So if you wanted to copy the contents of variable J into variable I,

you would use the statement:

      JSR MOVE
      ADR J,I

This is comparable to the Basic statement:

      I=J

Always remember that MOVE copies the contents of an integer variable into another variable.
If you use a constant as the first operand (or second operand for that matter),
MOVE will simply go to the address in memory specified by that constant, get the two bytes,
and store them into the destination variable.

I should point out that SPEED/ASM does very little type and range checking. MOVE and LOAD
simply move values around. They don’t care if you’re actually dealing with integer variables.

They store two bytes into the address you specify regardless of whether the variable is a
character, integer, floating point, string variable, or even a 6502 instruction.

Therefore you should take care that the destination operand of the LOAD routine and both
operands of the MOVE routine are the names of properly defined integer variables in your
program.

The Absolute Value and Negation Routines
SPEED/ASM provides two routines for negating and calculating the absolute value of an
integer variable.

The ABS routine (see Listing 1 for the equate for ABS) is invoked using the calling sequence:

      JSR ABS
      ADR <name>

This routine will take the variable whose name appears after the ADR pseudo-opcode,
compute its absolute value, and store the absolute value back into the variable.

This routine performs the same function as the Basic statement:

      I = ABS(I)

Upon return from the ABS routine the overflow flag will be clear if the absolute value
function was performed properly. If the user attempted to take the absolute value of —32768
(an error condition) then the overflow flag will be returned set.

You can use the BVC and BVS instructions to test for this error condition.

The SPEED/ASM negate routine is used like the ABS routine; the only difference is that the
sign is inverted with the negate routine instead of always returning a positive value
(as with the ABS function). If the integer variable was negative, the NEG routine will
make it positive. If the variable was positive, NEG will make it negative.

NEG uses the calling sequence:

      JSR NEG
      ADR <name>

and is equivalent to the Basic statement:

      = -I

Since ABS and NEG operate on the variable in place, you may want to use the MOVE routine
to copy the variable into another location before calling the ABS or NEG routines.
For instance, if you wanted to perform the Basic instruction,

    I= ABS(J)

using the SPEED/ASM statements,

      JSR ABS
      ADR J
      JSR MOVE
      ADR J,I

does not perform the same operation. It leaves the absolute value of J in both I and J.

While in this simple example I easily could have moved the data into I and then taken the
absolute value of I, this would be impossible in more complex situations.

To handle situations like this, simply move J into some temporary location, take the
absolute value of that location, then operate on the data in this temporary location as
you wish.

      TEMP = (Y MOD Z)
      TEMP = TEMP * X
      TEMP = TEMP + 2
      TEMP = X/TEMP
      TEMP = J + 3
      TEMP = TEMP1 * TEMP
      I = TEMP - 55
        Example 7.

The Multiplication, Division and Modulo Functions
The 6502 doesn’t support the multiplication, division and modulo (remainder) operations
within its instruction set. To make up for the lack of these instructions in the 6502
instruction set, the SPEED/ASM package provides three routines to perform these operations
for you: The MUL, DIV and MOD routines. All three routines use the same format and calling
sequence.

The calling sequence is:

      JSR MUL     ;Or DIV Or MOD
      ADR <IVAR1>,<IVAR2>,<IVAR3>

*************************
      JSR MOD
      ADR Y,Z,TEMP

      JSR MUL
      ADR TEMP,X,TEMP

      CLC
      LDA TEMP
      ADC #2
      STA TEMP
      LDA TEMP +1
      ADC /2
      STA TEMP +1

      JSR DIV
      ADR X,TEMP,TEMP

      CLC
      LDA J
      ADC #3
      STA TEMP1
      LDA J#l
      ADC /3
      STA TEMP1 +1

      JSR MUL
      ADR TEMP,TEMP1, TEMP

      SEC
      LDA TEMP
      SBC #55
      STA I
      LDA TEMP +1
      SBC (55
      STA I+]
        Example 8.

This performs the operation:

      “<IVAR3> =<IVARI>*<IVAR2>”

If the division or modulo operation is called, then the operation performed is

      “<IVAR3> =<IVAR1>/<IVAR2>”

   or

      "<IVAR3> =<IVAR1>MOD<IVAR2>”

The 6502 overflow flag is returned set if overflow occurred while performing a
multiplication or if a division by zero occurred during the execution of the DIV or MOD
routines.

Unless you are quite sure that overflow or underflow will not occur, you should always
follow a call to MUL, DIV or MOD with a BVC or BVS instruction to test the validity of
the result.

Converting Complex Equations to The SPEED/ASM Format
The arithmetic routines (with the exception of the ABS and NEG routines) all require
exactly three parameters.

Basic, on the other hand, allows a rich variety of operations within a single statement.

In Basic you could type:

      I = (J + 3) * (X / (2 + X * (Y MOD Z))) —55

Such a statement cannot be translated to a single statement in SPEED/ASM.

Rather, the statement is broken down into the sequence of binary operations that make up
this equation and the individual operations are handled by calls to SPEED/ASM routines.

The previous equation would be broken down to the operations given in Example 7.

This code would be converted to the SPEED/ASM statements in Example 8. For purposes of
clarity, the tests for overflow were omitted from this code.

But it should help demonstrate how you translate a Basic expression into a sequence of
SPEED/ASM routine calls.

The Random Number Function RND
The SPEED/ASM package provides a function that returns a random number every time it’s called.

The calling sequence is:

      JSR RND
      ADR <IVAR>

When ever RND is called it stuffs a pseudo-random number in the range 0. . .32767 and
stores it in the variable that follows the JSR. If you wish to generate a random number
in the range 0...n then call the random number generator and use the MOD routine.

For example, to get a number in the range 0...25 you should use the code given in Example 9.

      JSR LOAD
      ADR 26, TEMP
      JSR RND
      ADR  RNDVAL
      JSR MOD
      ADR — RNDVAL,TEMP,RNDVAL

        Example 9.

Note that the mod of RNDVAL and 26 was taken. This produces a value in the range of 0... 25.

Performing I/O in SPEED/ASM
Before discussing integer I/O in SPEED/ASM, I should first introduce character I/O,
since numeric I/O is dependent upon character I/O. Five routines are associated with
character I/O in SPEED/ASM: GETC, PUTC, READLN, HOME and INIT.

The INIT routine, as I’ve already mentioned, must be called before calling any SPEED/ASM
routines. In particular it must be called before performing any I/O routines since several
pointers and counters used by the I/O package are initialized by INIT.

Failing to call INIT before performing an I/O operation may result in garbled data.

      ENTRNUM      JSR   READLN
                   JSR   RDINT
                   ADR   J
                   BVC   GOODNUM
      ;
                   CMP   #0
                   BEQ   BADNUM
                   BMI   RANGERR
      ;
      ; Must be one at this point
      ;
      BADNUM       JSR   PRINT
                   BYT   "Bad character in number, re-enter",CR,0
                   JMP   ENTRNUM
      ;
      RANGERR      JSR   PRINT
                   BYT   "Value out of range, re-enter",CR,0
                   JMP
      ;
      ;
      GOODNUM      ---   ;Continue processing here
                Example 10.

HOME is used to clear the screen and position the cursor in the upper left corner.
This routine is included in the SPEED/ASM package to obtain a certain amount of machine
independence, By placing this jump in the SPEED/ASM code (instead of the user program),
it will have to be changed in only one location if you want to move the program to a
computer other than the Apple II.

Versions of SPEED/ASM will eventually be available for the Atari, PET, VIC and other 6502
computers, allowing you to easily move a program from one computer to another. 

HOME’s purpose is to help minimize the machine dependent code.

All input from the system console is handled line by line. Any time you read a character,
number or string from the keyboard, the SPEED/ASM routines will read the data from the
current line input buffer. If the buffer is empty, the user is prompted to enter a new
line from the keyboard. This works fine until you prompt the user for some input
(expecting him to enter a new line from the keyboard) and the SPEED/ASM package uses the
last few characters on the previous line as the input. To insure that the next input
performed takes its data from the beginning of a new input line, you should call the
READLN (read a line) routine to force the user to enter a new line of data. READLN will
wait until the user types in a complete line of text and then it will continue execution
with the next statement following the call to the READLN routine.

The GETC routine reads a single character from the current line buffer and returns it in
the 6502 accumulator. If the line buffer is empty, a new line is read from the keyboard
and GETC returns the first character on that line. I must point out that if there are
characters in the input line buffer the keyboard will not be read.

Instead, the next available character in the input buffer will be returned in the 6502
accumulator. If you need to read the character from the keyboard, always call READLN
before calling GETC.

The final character I/O routine is the PUTC routine. PUTC takes the character in the 6502
accumulator and outputs it to the console screen. One nice feature of the PUTC routine is
that it will automatically convert: lowercase to uppercase if the end user of your program
cannot display lowercase on his Apple. If your system has a lowercase adapter,
like the Lazer MicroSystems’ Lower Case + Plus and Keyboard + Plus modules, then you can
write your SPEED/ASM programs using easy-to-read lowercase without having to worry about
in-compatibility problems.

The READLN, GETC and PUTC routines are primitive routines.

All other I/O routines can be synthesized from these three subprograms.

When I talk about character operations we'll return to the discussion of the GETC and
PUTC routines.

Using PRINT to Print String Literals
I've already used the SPEED/ASM PRINT routine in several examples.

A formal definition of the PRINT routine will help explain its use in your
SPEED/ASM programs.

The PRINT routine is used to print a sequence of ASCII characters to the Apple’s video screen.
This routine prints every character following the JSR PRINT instruction up to, but not
including, a zero terminating byte. Upon encountering a zero byte, the PRINT routine
terminates output, and control is returned to the 6502 instruction that follows the zero
byte.

PRINT is useful for printing messages, prompts and other string literal output.
PRINT does not automatically eject a carriage return after the string is printed.

If you wish to output a carriage return you must explicitly include the ASCII code for
the carriage return in your output string;

i.e.,

      JSR PRINT
      BYT “STRING followed by Return”,CR,0

Note that CR was used instead of the actual code for carriage return ($8D).

CR is a symbol, defined in the SPEED/ASM equates, which is replaced by the value $8D.

Since PRINT will print all characters up to the terminating zero byte, multiple lines can
be output using a single call to the PRINT subroutine. Simply separate each line with a
carriage return and PRINT will output the text on several lines:

      JSR PRINT
      BYT "This is the first line, and it is followed by",CR
      BYT "this second line.",CR,0

Other than improving the readability of the program, the separate lines need not appear
on separate source lines as in this example. The second string could have immediately
followed the CR on the first line. This type of coding, however, is not recommended
because it makes the source file much harder to read.

Performing Integer I/O In SPEED/ASM
Operating on integer values is one of the primary functions you will do in SPEED/ASM.
However, these operations are almost useless unless you can communicate the results of
these operations to the world outside the computer.

Two routines are provided in the SPEED/ASM package to facilitate integer I/O: RDINT
(read an integer) and PRTINT (print an integer).

Printing an integer using the PRTINT routine is easy — just follow the JSR PRTINT with
the address of the integer you want to print.

For example, if you wanted to print the contents of the integer variable I onto the
Apple’s video screen you would use the statement(s):

      JSR PRTINT
      ADR I

and the contents of I would be displayed for you. In the next installation of this series
I will discuss how to format this output to create a pretty listing.

The RDINT routine is a little more complicated to use than the PRTINT routine because
there is the possibility that an error condition might occur.

The RDINT routine expects the user to type a valid numeric integer which takes the form:

1) Any number of leading blanks, commas or carriage returns, followed by
2) An optional minus sign, followed by
3) One to five digits forming a value in the range 0. . . 32767, followed by
4) A space, comma or carriage return.

If the numeric string is of the proper format then SPEED/ASM will store the value into
the integer variable whose address follows the JSR;

e.g.,

      JSR RDINT
      ADR J

will read an integer variable from the line input buffer (reading a new line if necessary)
and store the numeric value into J.

If an input error occurs, then the V flag will be returned set so you can use the BVS or
BVC instruction to test for the error condition.

Three error conditions can be returned in the 6502 accumulator.
If the overflow flag is set, then the accumulator contains zero if the last character of
the number wasn’t a space, comma or carriage return.

This error condition can be considered optional.

If you want to allow characters other than space, comma and carriage return at the end
of a number, you can ignore this error.

If the overflow flag is set and the accumulator contains one, the first character of the
number was not a valid digit or minus sign. All preceding spaces, commas and carriage
returns were stripped before the failure to obtain a digit or minus sign was detected.

This is a definite error and your program should prompt the user to re-enter the data.

If the overflow flag was set and the accumulator contained $8D, the value entered by the
user was greater than 32767 or less than —32768.

Obviously this number must be re-entered by the user.

A program that would prompt the user to re-enter on an entry error is shown in Example 10.

I have included additional examples in Listing 2.

Looking Forward
So far the examples have been rather trivial since the SPEED/ASM routines presented thus
far haven't included the necessary looping, conditional, and transfer of control routines.

Next time I’ll start discussing program control structures so that you will be able to
start writing fairly complex programs, See ya next time!
